package com.icehand.redis.interceptor;

import com.icehand.redis.annotation.CacheLock;
import com.icehand.redis.exception.IcehandRedisException;
import com.icehand.redis.key.CacheKeyGenerator;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;

/**
 * @author icehand
 */
@Aspect
@Slf4j
public class LockMethodInterceptor {

    private final StringRedisTemplate lockRedisTemplate;
    private final CacheKeyGenerator cacheKeyGenerator;

    public LockMethodInterceptor(StringRedisTemplate lockRedisTemplate, CacheKeyGenerator cacheKeyGenerator) {
        this.lockRedisTemplate = lockRedisTemplate;
        this.cacheKeyGenerator = cacheKeyGenerator;
    }

    @Around("execution(public * *(..)) && @annotation(com.icehand.redis.annotation.CacheLock)")
    public Object interceptor(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        CacheLock lock = method.getAnnotation(CacheLock.class);
        if (StringUtils.isEmpty(lock.prefix())) {
            throw new IcehandRedisException("lock key can not be null...");
        }

        final String lockKey = cacheKeyGenerator.getLockKey(pjp);
        try {
            // 采用原生 API 来实现分布式锁
            final Boolean success = lockRedisTemplate.execute(
                    (RedisCallback<Boolean>) connection ->
                            connection.set(lockKey.getBytes(),
                                    new byte[0],
                                    Expiration.from(lock.expire(), lock.timeUnit()),
                                    RedisStringCommands.SetOption.SET_IF_ABSENT));
            if(null == success) {
                if(log.isDebugEnabled()){
                    log.debug("set lock 【{}】 fail",lockKey);
                }
                throw new IcehandRedisException("set lock fail");
            }

            if (!success) {
                if(log.isDebugEnabled()){
                    log.debug("get lock 【{}】 fail",lockKey);
                }
                throw new IcehandRedisException("get lock fail");
            }

            try {
                return pjp.proceed();
            } catch (Throwable throwable) {
                throw new IcehandRedisException(throwable);
            }
        } finally {
             lockRedisTemplate.delete(lockKey);
        }
    }
}